package hw;

import java.io.File;
import java.io.IOException;

public class P2PServer {
	private AbstractServer server;
	private File p2pfileToShare;
	private FileChangesBroadcasterThread fileChangesBroadcasterThread;
	private long lastModified;
	private long lastHeardOf;
	private long lastWroteToFile;
	private final Object lock = new Object();
	public P2PServer(final File fileToShare,final int port,final int synctime) throws IOException {
		if (fileToShare.length() > Integer.MAX_VALUE) {
			throw new IOException("file is too big");
		}
		lastModified = lastHeardOf = lastWroteToFile = fileToShare.lastModified();
		p2pfileToShare = fileToShare;
		fileChangesBroadcasterThread = new FileChangesBroadcasterThread(synctime);
		fileChangesBroadcasterThread.addWatcher(new FileChangesWatcher(fileToShare));
		fileChangesBroadcasterThread.register(new FileChangesListener() {
			@Override
			public void onChange(final File f) {


				synchronized (lock) {
					if (lastHeardOf <= f.lastModified() && lastWroteToFile != f.lastModified()) {
						Report.report("There was a local update on the file made by user broadcasting...");
						final Sync sync = new Sync(lastModified = lastHeardOf = f.lastModified());
						lastWroteToFile=f.lastModified();
						broadcast(sync);
					} else {
						Report.report("There was a local update on the file made by the system.");
					}
				}

			}
		});



		server=new AbstractServer<Bundle>(port) {

			@Override
			protected void handleMessageFromClient(final Bundle msg,final ConnectionToClient client) {
				if (!(msg instanceof Bundle)) return;
				synchronized (lock) {
					Report.report("Server of this node Recieved " + msg + " msg.lastModified = "+ Report.format(msg.getLastModified())  + " lastModified = " + Report.format(lastModified) + " lastHeardOf = " + Report.format(lastHeardOf) + " lastWroteToFile = "+ Report.format(lastWroteToFile)  );
					handleMessege(msg,client);
				}
			}
		};
	}


	protected void handleMessege(final Bundle msg,final Object sendBack) {
		if (msg instanceof Sync) {
			if (msg.getLastModified() >= lastHeardOf && msg.getLastModified() > lastModified) {
				Report.report("Recieved a sync about a new version from some node which this node doesnt have returns sync with lastModified to indicate this node has older file.");
				lastHeardOf=msg.getLastModified();
				sendBack(sendBack,new Sync(lastModified));
			} else if (msg.getLastModified() < lastModified) {
				if (lastModified == lastHeardOf) {
					Report.report("Recieved a sync about some node that holds an older version returns the file.");
					try {
						sendBack(sendBack,new FileData(p2pfileToShare,lastModified));
					} catch (final IOException e) {
						Report.report("couldnt send file");
					}
				}
			} 
		} else if (msg instanceof FileData) {
			if (msg.getLastModified() >= lastHeardOf && msg.getLastModified()>lastModified) {
				try {
					Report.report("Recieved a new version of the file.");
					((FileData) msg).writeToFile(p2pfileToShare);
					lastModified = lastHeardOf = msg.getLastModified();
					lastWroteToFile = p2pfileToShare.lastModified();
					broadcast(new Sync(lastModified));
				} catch (final IOException e) {
					Report.report("locally write failed");
				}
			} 
		} 
	}


	private void sendBack(final Object sendBack, final Bundle sync) {
		new Thread() {
			@Override
			public void run() {
				try {
					if (sendBack instanceof ConnectionToClient) 
						((ConnectionToClient) sendBack).sendToClient(sync);
					else if (sendBack instanceof AbstractClient) 
						((AbstractClient<Bundle>) sendBack).sendToServer(sync);
				} catch (final Exception e) {
					Report.report("couldnt send back bundle to sender, broadcasting to try to recover...");
					broadcast(sync);
				}
			}
		}.start();
	}


	protected void broadcast(final Bundle sync) {
		new Thread() {
			@Override
			public void run() {
				server.sendToAllClients(sync);
				
			}
		}.start();

	}




	public void start() throws IOException {
		Report.report("Initiailing : ");	
		Report.report("Starting to listen to connections from all (incmoning) neighbors...");	
		server.listen();
		Report.report("Starting to listen to local changes in file...");	
		fileChangesBroadcasterThread.start();
		Report.report("App is fully initialized, its recommanded that you wait a few seconds to higher the probability that this machine would have the latest file version.");
	}








}
